package butines.core.game;

import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;

public class UIManager implements InputListener {

	private static UIComponent componentFocus;
	private static UIComponent componentOver;
	private static UIComponent componentDrag;
	
	private GameContext gameContext;
	private Input input;
	private Audio audio;
	
	private List<UIComponent> root;

	public UIManager(GameContext gameContext) {
		this.gameContext = gameContext;
	}
	
	protected void init() {
		input = gameContext.getInput();
		input.addListener(this);
		
		UIComponent.setManager(this);
	}
	
	protected void destory() {
		input.removeListener(this);
		UIComponent.setManager(null);
	}

	public Input getInput() {
		return input;
	}

	public Audio getAudio() {
		return audio;
	}
	
	public GameContext getGameContext() {
		return gameContext;
	}
	
	public List<UIComponent> getRoot() {
		if (root == null) {
			root = new ArrayList<UIComponent>();
		}
		return root;
	}

	private void setComponentFocus(UIComponent component) {
		if (componentFocus == component) {
			return;
		}
		if (componentFocus != null) {
			componentFocus.fireFocus(false);
		}
		componentFocus = component;
		if (componentFocus != null) {
			componentFocus.fireFocus(true);
		}
	}
	
	private void setComponentOver(UIComponent component) {
		if (componentOver == component) {
			return;
		}
		if (componentOver != null) {
			componentOver.fireMouseOver(false);
			cancelDrag();
		}
		componentOver = component;
		if (componentOver != null) {
			componentOver.fireMouseOver(true);
		}
	}
		
	private boolean isMouseOver(float sx, float sy, UIComponent comp) {
		return (sx < comp.getLeft() || sy < comp.getTop() || sx > comp.getRight() || sy > comp.getBottom()) == false;
	}
	
	private UIComponent findComponent(int sx, int sy, UIComponent comp) {
		UIComponent found = null;
		if (isMouseOver(sx, sy, comp)) {
			found = comp;
			for (UIComponent next: comp.getComponents()) {
				UIComponent tmp = findComponent(sx, sy, next);
				if (tmp == null) {
					continue;
				}
				found = tmp;
				break;
			}
		}
		return found;
	}
	
	public void onKey(int keyCode, boolean pressed, float tpf) {
		if (componentFocus != null) {
			componentFocus.fireKey(keyCode, pressed);
		}
	}
	
	public void onButton(int sx, int sy, int button, boolean pressed, float tpf) {
		if (pressed) {
			setComponentFocus(componentOver);
		}
		if (componentFocus != null) {
			float vx = componentFocus.toInnerX(sx);
			float vy = componentFocus.toInnerY(sy);
			if (button == Input.BUTTON_1 && pressed == true) {
				startDrag(vx, vy, componentFocus);
			}
			if (button == Input.BUTTON_1 && pressed == false) {
				endDrag();
			}
			componentFocus.fireButton(vx, vy, button, pressed);
		}

	}
	
	public void onMove(int sx, int sy, int dx, int dy, float tpf) {
		for (UIComponent comp: getRoot()) {
			UIComponent found = findComponent(sx, sy, comp);
			setComponentOver(found);
			if (found != null) {
				break;
			}
		}
		if (componentOver != null) {
			float vx = componentOver.toInnerX(sx);
			float vy = componentOver.toInnerY(sy);
			componentOver.fireMove(vx, vy, dx, dy);
		}
		updateDrag(sx, sy);
	}
	
	public void onWheel(int x, int y, int wheel, float tpf) {
		
	}

	private void update(float tpf, UIComponent comp) {
		comp.fireUpdate(tpf);
		if (comp == componentDrag && !componentDrag.isIgnoreDrag()) {
			fireSelect();
		}
		for (UIComponent sub: comp.getComponents()) {
			update(tpf, sub);
		}
	}
	
	protected void notifyUpdate(float tpf) {
		if (componentFocus != null) {
			componentFocus.fireScroll(tpf);
		}
		for (UIComponent comp: getRoot()) {
			update(tpf, comp);
		}
	}
	
	private void render(Graphics2D g, UIComponent comp) {
		Graphics2D clip = (Graphics2D) g.create(comp.getLeft(), comp.getTop(), comp.getWidth(), comp.getHeight());
		clip.translate(-Math.round(comp.getOffsetX()), -Math.round(comp.getOffsetY()));
		comp.fireRender(clip);
		if (comp == componentDrag && !componentDrag.isIgnoreDrag()) {
			drawSelect(g);
		}
		for (UIComponent sub: comp.getComponents()) {
			render(clip, sub);
		}
		clip.dispose();
		clip = null;
	}
	
	protected void notifyRender(Graphics2D g) {
		for (UIComponent comp: getRoot()) {
			render(g, comp);
		}
	}

	protected boolean isFocus(UIComponent comp) {
		return componentFocus == comp;
	}
	
	protected boolean isMouseOver(UIComponent comp) {
		return componentOver == comp;
	}

	protected void requestFocus(UIComponent comp) {
		setComponentFocus(comp);
	}
	
	private static final int STATE_RELEASED = 0;
	private static final int STATE_PRESSED  = 1;
	private static final int STATE_DRAGGED  = 2;
	private static final int STATE_WAITING_FOR_RELEASE = 3;
	
	private int state = STATE_RELEASED;
	
	private float xPressed;
	private float yPressed;
	private float xLast;
	private float yLast;
	private float xSelect;
	private float ySelect;
	private float wSelect;
	private float hSelect;

	private Color colorInner  = new Color(.0f, .95f, .0f, .20f);
	private Color colorBorder = new Color(.0f, .90f, .0f, .50f);
	
	private void cancelDrag() {
		if (state == STATE_PRESSED || state == STATE_DRAGGED) {
			this.state = STATE_RELEASED;
			componentDrag.setScrollDisabled(false);
			componentDrag = null;
		}
	}
	
	private boolean startDrag(float x, float y, UIComponent comp) {
		if (state == STATE_RELEASED || state == STATE_WAITING_FOR_RELEASE) {
			xSelect = xPressed = x;
			ySelect = yPressed = y;
			wSelect = 0;
			hSelect = 0;
			state = STATE_PRESSED;
			componentDrag = comp;
			componentDrag.setScrollDisabled(true);
			return true;
		}
		return false;
	}
	
	private boolean endDrag() {
		if (state == STATE_PRESSED || state == STATE_DRAGGED) {
			state = STATE_WAITING_FOR_RELEASE;
			componentDrag.setScrollDisabled(false);
			return true;
		}
		return false;
	}
	
	private void updateDrag(float x, float y) {
		xLast = x;
		yLast = y;
		if (state == STATE_PRESSED || state == STATE_DRAGGED) {
			// Recupera a extremidade esquerda (x) e acima (y)	
			xSelect = Math.min(xPressed, xLast);
			ySelect = Math.min(yPressed, yLast);
			// Atualiza o tamanho da area sinalizada
			wSelect = Math.abs(xPressed - xLast);
			hSelect = Math.abs(yPressed - yLast);
			// Se ocorre evento de arrastar muda o cursor e sinaliza o evento
			if (state != STATE_DRAGGED && wSelect > 1 && hSelect > 1) {
				state = STATE_DRAGGED;
			}
		}
	}
	
	protected boolean isInsideOfSelection(float x, float y, float r) {
		return (x >= xLast - r && x <= xLast + r && y >= yLast - r && y <= yLast + r) 
			|| ((state == STATE_DRAGGED || state == STATE_WAITING_FOR_RELEASE)
			&& x >= xSelect - r && x <= xSelect + wSelect + r && y >= ySelect - r && y <= ySelect + hSelect+ r);
	}

	private void fireSelect() {
		if (state == STATE_WAITING_FOR_RELEASE) {
			componentDrag.fireSelect(xSelect, ySelect, wSelect, hSelect);
			state = STATE_RELEASED;
			xSelect = ySelect = 0;
			wSelect = hSelect = 0;
		}
	}
	
	private void drawSelect(Graphics2D g2d) {
		if (state == STATE_DRAGGED) {
			int x = Math.round(xSelect);
			int y = Math.round(ySelect);
			int w = Math.round(wSelect);
			int h = Math.round(hSelect);
			g2d.setColor(colorInner);
			g2d.fillRect(x, y, w, h);
			g2d.setColor(colorBorder);
			g2d.drawRect(x, y, w, h);
		}
	}
	
}
